/*
 * @Author: 吴世扬 18368095041@163.com
 * @Date: 2024-07-09 11:59:14
 * @LastEditors: 吴世扬 18368095041@163.com
 * @LastEditTime: 2024-07-12 10:24:01
 * @FilePath: /vue3-uniapp/src/other/esbuildPlugins/pnpmPathPlugin.ts
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
import type {
    Plugin,
    PluginBuild,
    OnResolveArgs,
    ResolveResult
} from 'esbuild';
import { resolve as pathResolve, join, dirname } from 'path';
import { lstatSync, readlink, lstat, readlinkSync } from 'fs';

// 从绝对路径获取node_modules的绝对路径或从绝对路径获取获取符号链接绝对路径的node_modules的绝对路径
const getNodeModulesByAbsolutePath = async (path: string) => {
    const stats = lstatSync(path);
    if (stats.isSymbolicLink()) {
        const relativePath = await new Promise<string>((resolve, reject) => {
            readlink(path, async (err, resolvedPath) => {
                if (err) return reject(err);
                resolve(resolvedPath);
            });
        });
        const baseDirPath = dirname(path);
        const absolutePath = pathResolve(baseDirPath, relativePath);
        const nodeModulesIndex = absolutePath.lastIndexOf('node_modules');
        if (nodeModulesIndex === -1)
            Promise.reject(
                new Error(
                    `[pnpmPathPlugin] ${path}对应的符号链接下没有node_modules`
                )
            );

        const parentNodeModulesPath = absolutePath.substring(
            0,
            nodeModulesIndex + 'node_modules'.length
        );
        return Promise.resolve({
            isSymbolicLink: true,
            nodeModules: parentNodeModulesPath
        });
    } else {
        const nodeModulesIndex = path.lastIndexOf('node_modules');
        if (nodeModulesIndex === -1)
            Promise.reject(
                new Error(`[pnpmPathPlugin] ${path}路径下没有node_modules`)
            );

        const parentNodeModulesPath = path.substring(
            0,
            nodeModulesIndex + 'node_modules'.length
        );
        return Promise.resolve({
            isSymbolicLink: false,
            nodeModules: parentNodeModulesPath
        });
    }
};

// 从最后的nodeModules后获取路径数组
const getLastNodeModulesPathList = (path: string) => {
    const arr = path.split('/');
    let isPush = false;
    const paths: string[] = [];
    const lastIndex = arr.findLastIndex((item) => item === 'node_modules');
    arr.reduce((previous, current, currentIndex) => {
        let returnVal = previous;
        if (current) {
            returnVal = `${returnVal}/${current}`;
            if (isPush) {
                paths.push(returnVal);
            }
            if (current === 'node_modules' && currentIndex >= lastIndex) {
                isPush = true;
            }
        }
        return returnVal;
    }, '');
    return paths;
};

// 用esbuild的build.resolve处理路径
const resolvePath = async (
    paths: string[],
    args: OnResolveArgs,
    resolve: PluginBuild['resolve']
) => {
    try {
        const { path: importPath, importer, kind } = args;
        for (let i = 0; i < paths.length; i++) {
            const path = paths[i];
            const { isSymbolicLink, nodeModules } =
                await getNodeModulesByAbsolutePath(path);
            if (isSymbolicLink) {
                const result: ResolveResult = await resolve(importPath, {
                    kind,
                    importer,
                    resolveDir: nodeModules
                });
                return Promise.resolve(result);
            }
        }
        throw new Error(
            `[pnpmPathPlugin]无法处理${importer}里的"${importPath}"`
        );
    } catch (e) {
        return Promise.reject(e);
    }
};

export const pnpmPathPlugin = (): Plugin => {
    return {
        name: 'pnpmPathPlugin',
        setup(build) {
            build.onResolve(
                { filter: /^[@a-zA-Z][a-zA-Z].*$/, namespace: 'file' },
                async (args) => {
                    const {
                        importer,
                        resolveDir,
                        namespace,
                        kind,
                        path: importPath
                    } = args;
                    // console.log(args);
                    // 非node_modules的包引用的包直接默认esbuild导入逻辑处理
                    if (importer.indexOf('node_modules') === -1) {
                        return;
                    }
                    const result = await build.resolve(importPath, {
                        kind,
                        resolveDir
                    });
                    // 通用逻辑不报错返回，否则直接下一步处理符号链接导致无法找到路径的问题
                    if (result.path && result.errors.length === 0) {
                        // console.log(111, args, result);
                        return Promise.resolve({
                            ...result
                        });
                    }
                    return new Promise(async (resolve, reject) => {
                        try {
                            // node_modules的包引用的包需特殊处理，因为pnpm包依赖目录结构不是平铺的
                            const paths =
                                getLastNodeModulesPathList(resolveDir);
                            const result = await resolvePath(
                                paths,
                                args,
                                build.resolve
                            );
                            // console.log(222, args, result);
                            resolve({
                                ...result
                            });
                        } catch (e) {
                            return reject(e);
                        }
                    });
                }
            );
        }
    };
};
